import { isArray, isObject } from "../utils";;
import { arrayMethods } from "./array";
import Dep from "./dep";

//每个对象都有一个__proto__ 它指向所属类的原型 fn.__proto__=Function.prototype
//每个原型上都有一个constructor属性 指向函数本身 Function.prototype.constructor = Function

data:{
    arr:[1,2,3]
}
class Observer{
    constructor(value){
        //不让__ob__被遍历到
        //value.__ob__ = this;//给数组和对象添加一个属性 这个写法会递归死循环

        //如果一个对象添加一个不存在的属性我希望也能更能更新视图 {c:1}.__obj__.dep => watcher this是object的实例 vue.$set的原理
        this.dep = new Dep();//value是对象或者数组 value.__obj__.dep 给对象和数组都增加dep属性 {} []
        Object.defineProperty(value, '__ob__', {
            value:this,
            enumerable:false //标识这个属性不能被枚举出来 不能被循环到
        })

        if(isArray(value)){
            //更改数组原型方法(有兼容性问题可以采用循环的方式)
            value.__prop__ = arrayMethods;
            this.observeArray(value)
        }else{
            this.walk(value);//核心就是循环对象
        }
    }
    observeArray(data){//递归遍历数组 对数组内部的对象再次重写 [[]] [{}]
        data.forEach(item =>observe (item));//组件里面如果是引用类型那么是响应式的
    }
    walk(data){//循环对象且不要原型上的方法
        Object.keys(data).forEach(key=>{//要使用definedProperty重新定义
            defineReactive(data,key,data[key])
        })
    }
}

//vue2 应用了defineProperty需要一加载的时候 就进行递归操作 所以好性能 如果层次过深也会浪费性能
//1 性能优化的原则
//1) 不要把所有的数据都放在data中 因为所有的数据都会增加get set
//2) 不要写数据的时候 层次过深 尽量扁平化数据
// 3) 不要频繁获取数据
// 4) 如果数据不需要使用相应式 可以使用object.freeze冻结属性
// 5) observe去观测data中的数据 和vm没关系 说明datat已经变成响应式
// 6.vm上像取值也能取到data中的数据 vm._data = data 这样用户就能取到data值了 vm._data
// 7.用户觉得有点麻烦 vm.xxx => vm._data
//JSON.stringfy 会取值收集键 但不会收集对象本身
function dependArray(value) {//[[],{}]让数组了的引用类型都收集依赖
    for(let i=0;i<value.length;i++){
        let current = value[i];
        current.__obj__&&current.__obj__.dep.depend();
        if(Array.isArray(current)){
            dependArray(current);
        }
    }
}

function defineReactive(obj,key,value) {//vue2 慢的原因 主要在这个方法中
    let childOb = observe(value);//递归进行观测数据 不管多少层 都进行defineProperty
    //childOb 如果有值的话 那么就是数组或者对象
    //数组对应dep

    if (value.__ob__){
        return;//一个对象不需要重新被观测
    }

    let dep = new Dep();//每个属性都增加了一个dep

    Object.defineProperty(obj, key, {
        get(){//后续会有很多逻辑
            if(Dep.target){
                dep.depend();
                if (childOb) {//取属性的时候 会对对象本身和数组进行依赖收集
                    childOb.dep.depend();//让对象和数组也记住当前的watcher
                    if (Array.isArray(value)) {//对于对象 JSON.stringfy
                        dependArray(value);
                    }
                }
            }
            return value;//闭包 此value会像上层的value进行查找
        },
        set(newValue){//如果设置的是一个对象 会再次进行劫持
            if(newValue===value) return;
            observe(newValue);
            value = newValue;
            dep.notify();//拿到房前的dep里面的watcher依次执行
        }
    })
}

export function observe(value) {
    // 1.如果value不是对象，就不用观测了 说明写的有问题
    if(!isObject(value)){
        return;
    }
    //需要对对象进行观测(最外层必须是一个{} 不能是数组)
    //如果一个数据已经被观测过了，就不要再观测了，用类来实现，我观测过了就增加一个标识，说明观测过了，在观测的时候，可以先检测是否观测过 如果观测过了就跳过检测
    return new Observer(value)
}

